本页目录

裸指针与 FFI

*const T*mut T 不受借用检查器约束——可以随意别名、可以不指向合法内存。它们存在的唯一理由是 FFI:调用 C 库、接收 C 的回调、操作硬件寄存器。unsafe 块不是"禁用安全检查",而是把安全责任从编译器转移到程序员。

裸指针: 没有 borrow checker 的指针

let x = 42;
let ptr: *const i32 = &x;            // 引用 → 裸指针 (安全)
unsafe {
    println!("{}", *ptr);            // 解引用 (unsafe — 编译器不验证有效性)
}

裸指针不需要遵守 borrow checker 规则——你可以有多个 *mut T 指向同一数据、可以是 null、可以不保证 alignment。所有 responsibility 转移到程序员。

与 C int * 的区别:Rust 区分 *const T*mut T(类似 C 的 const int * vs int *)。但 C 可以隐式丢弃 const,Rust 不行。

extern "C": C 函数接口

extern "C" {
    fn abs(input: i32) -> i32;       // 声明 C 函数
}

#[no_mangle]                         // 禁止 Rust name mangling
pub extern "C" fn rust_callback(x: i32) -> i32 {
    x + 1                            // C 代码可以调用这个函数
}

extern "C" 使用 C calling convention:参数在寄存器/栈中按目标平台的 C ABI 排列。x86-64 上这是 System V AMD64 ABI(rdi, rsi, rdx, rcx, r8, r9 传参)。也可以使用 extern "system" (Windows API) 和 extern "Rust" (默认)。

#[repr(C)]: C 兼容的内存布局

#[repr(C)]
struct MyStruct {
    a: i32,                          // offset 0
    // padding: 4 bytes               // C 要求 f64 对齐到 8
    b: f64,                          // offset 8
}                                    // size: 16 bytes

没有 #[repr(C)] 时,Rust 编译器可以重排字段顺序以优化 struct 大小。加上 #[repr(C)] 后,字段按声明顺序,按 C ABI 要求 padding——保证与 C struct 二进制兼容。#[repr(C, packed)] 消除所有 padding(但可能产生未对齐引用——unsafe)。

bindgen: 自动生成 Rust FFI 绑定

从 C header 生成 Rust 的 extern 声明、struct 定义和常量:

// build.rs:
fn main() {
    println!("cargo:rerun-if-changed=wrapper.h");
    let bindings = bindgen::Builder::default()
        .header("wrapper.h")
        .allowlist_function("my_lib_.*")   // 只 binding 这些函数
        .generate().expect("bindgen failed");
    bindings.write_to_file("src/bindings.rs").unwrap();
}
// src/lib.rs:
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

Callback: C 调 Rust 的陷阱

extern "C" fn callback(x: i32) {
    println!("C called with {}", x);
}
unsafe { register_callback(callback as unsafe extern "C" fn(i32)); }

Rust 函数作为 callback 传给 C 时,panic 不能跨越 FFI 边界⁠——如果 callback 中 panic,且 unwind 穿过 C 栈帧,这是 UB(C 栈上没有 unwind tables)。解决方案:在 callback 中用 std::panic::catch_unwind 捕获 panic,或在 Cargo.toml 中设 panic = "abort"

参考

  • Rustonomicon: FFI, C ABI
  • bindgen: rust-lang.github.io/rust-bindgen
  • The Embedded Rust Book: FFI and C interaction

*Keywords: raw pointer, *const, mut, extern "C", #[no_mangle], repr(C), bindgen, FFI callback, C ABI